package cn.xw.commons.controller;

import cn.xw.commons.core.domain.Result;
import cn.xw.commons.core.utils.fileServerUtils.FastDFSBeanToolsProxy;
import cn.xw.commons.core.utils.fileServerUtils.FastDFSTools;
import cn.xw.commons.core.utils.fileServerUtils.domain.dto.ManyFileDTO;
import cn.xw.commons.core.utils.fileServerUtils.domain.dto.SingleFileDTO;
import cn.xw.commons.core.utils.fileServerUtils.domain.entity.FileStream;
import cn.xw.commons.core.utils.fileServerUtils.domain.vo.FileDetailsVO;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.List;

/**
 * 文件服务工具控制器
 * // 单个上传、批量上传、查询、加密查询、删除、下载
 *
 * @author Anhui AntLaddie <a href="https://www.cnblogs.com/antLaddie/">(博客园蚂蚁小哥)</a>
 * @version 1.0
 **/
@RestController
@RequestMapping("/fileServer")
@RequiredArgsConstructor
public class FileServerController {

    // 属性注入：只有上传和删除调用此内部方法
    private final FastDFSBeanToolsProxy fastDFSBeanToolsProxy;

    /***
     * 业务图片一旦入库则需要手动删除缓存信息，一般在文件成功被业务数据库保存时，在保存成功后调用这个fileDelCleanCache方法
     * 注意：不是说通过这个接口调用，而是直接在业务代码内调用清理，不清理则会导致已经入库的图片被定时清理
     * @param singleFileDTO 文件url
     * @return String
     */
    @PostMapping("/businessSuccess")
    public Result<String> businessSuccess(@RequestBody SingleFileDTO singleFileDTO) {
        System.out.println("业务文件上传成功则需要手动清理缓存，需要清理：" + singleFileDTO.getUrl());
        fastDFSBeanToolsProxy.fileDelCleanCache(singleFileDTO.getUrl());
        return Result.ok("清除缓存成功");
    }

    /***
     * 单个文件上传
     * @param file 单个文件信息
     * @return 上传的URL信息
     */
    @PostMapping("/uploadFile")
    public Result<String> uploadFile(@RequestParam("file") MultipartFile file) throws IOException {
        // 构建文件上传的对象
        FileStream fileStream = new FileStream();
        fileStream.setFileName(file.getOriginalFilename());
        fileStream.setFileInputStream(file.getInputStream());
        // 文件上传并获取上传成功的资源定位URL
        return Result.ok(fastDFSBeanToolsProxy.uploadFile(fileStream));
    }

    /***
     * 批量文件上传
     * @param files 文件数组信息
     * @return 上传的URL信息
     */
    @PostMapping("/uploadFiles")
    public Result<List<String>> uploadFiles(@RequestParam("files") MultipartFile[] files) throws IOException {
        // 构建文件上传的对象
        List<FileStream> fileStreamList = new ArrayList<>();
        // 循环设置上传的文件信息
        for (MultipartFile file : files) {
            FileStream fileStream = new FileStream();
            fileStream.setFileName(file.getOriginalFilename());
            fileStream.setFileInputStream(file.getInputStream());
            fileStreamList.add(fileStream);
        }
        // 文件上传并获取上传成功的资源定位URL
        List<String> urls = fastDFSBeanToolsProxy.uploadFiles(fileStreamList);
        return Result.ok(urls);
    }

    /***
     * 单个地址加签，
     * @param url 需要加签的url资源地址，但是注意的是url必须是被URL编码的
     * @throws IOException IO异常
     */
    @GetMapping("/singleSignature")
    public Result<String> addSignatureAddress(String url) throws IOException {
        // URL解码 //编码使用：URLEncoder.encode()
        url = URLDecoder.decode(url, "utf-8");
        // url加签
        return Result.ok(FastDFSTools.addSignatureAddress(url));
    }

    /***
     * 多个地址加签（只有需要权限的地址才可以加签）
     * @param manyFileDTO 一个带有多个urls的请求资源；如：{"urls":["xx","xx"]}
     */
    @PostMapping("/manySignature")
    public Result<List<String>> addManySignatureAddress(@RequestBody ManyFileDTO manyFileDTO) {
        // 循环地址加签
        manyFileDTO.getUrls().replaceAll(FastDFSTools::addSignatureAddress);
        return Result.ok(manyFileDTO.getUrls());
    }

    /***
     * 根据资源定位URL来获取文件信息
     * @param singleFileDTO 一个带有url属性的请求资源；如：{"url":"xx"}
     * @return FileDetailsVO文件详情
     */
    @PostMapping("/fileDetails")
    public Result<FileDetailsVO> fileDetails(@RequestBody SingleFileDTO singleFileDTO) {
        return Result.ok(FastDFSTools.fileDetails(singleFileDTO.getUrl()));
    }

    /***
     * 根据资源定位URL来下载文件信息
     * @param singleFileDTO 一个带有url属性的请求资源；如：{"url":"xx"}
     * @param resp 响应对象，后面用来下载写出文件信息
     */
    @PostMapping("/download")
    public void download(@RequestBody SingleFileDTO singleFileDTO, HttpServletResponse resp) throws IOException {
        downloadsTools(Collections.singletonList(singleFileDTO.getUrl()), resp);
    }

    /***
     * 根据资源定位URL来下载文件信息
     * @param manyFileDTO 一个带有多个urls的请求资源；如：{"urls":["xx","xx"]}
     */
    @PostMapping("/downloads")
    public void downloads(@RequestBody ManyFileDTO manyFileDTO, HttpServletResponse resp) throws IOException {
        downloadsTools(manyFileDTO.getUrls(), resp);
    }

    /***
     * 根据资源定位URL来批量删除资源
     * @param singleFileDTO 一个带有url属性的请求资源；如：{"url":"xx"}
     */
    @PostMapping("/deleteUrl")
    public Result<String> deleteUrl(@RequestBody SingleFileDTO singleFileDTO) {
        Integer errno = fastDFSBeanToolsProxy.deleteUrl(singleFileDTO.getUrl());
        if (errno == 0) {
            return Result.ok("删除成功。");
        } else if (errno == 2) {
            return Result.fail("删除失败，资源url不存在！");
        } else {
            return Result.fail("删除失败！");
        }
    }

    /***
     * 根据资源定位URL来批量删除资源（不推荐，批量删除不会弹出某个文件的删除状况，但删除失败会记录到 fastDFS_del.log 文件日志）
     * @param manyFileDTO 一个带有多个urls的请求资源；如：{"urls":["xx","xx"]}
     */
    @PostMapping("/deleteUrls")
    public void deleteUrls(@RequestBody ManyFileDTO manyFileDTO) {
        FastDFSTools.deleteUrls(manyFileDTO.getUrls());
    }

    /***
     * 文件下载工具
     * @param urlList 下载的URL资源信息
     * @param resp 响应信息
     */
    private void downloadsTools(List<String> urlList, HttpServletResponse resp) {
        // 初始化需要写出流的对象封装
        FileStream fileStream = new FileStream();
        try {
            // 组装信息调用工具
            if (!urlList.isEmpty()) {
                // 若待下载的URL地址只有一个则走单个文件下载
                if (urlList.size() == 1) {
                    fileStream = FastDFSTools.singleDownload(urlList.get(0));
                }
                // 若待下载的URL地址存在多个则走批量文件下载
                if (urlList.size() > 1) {
                    fileStream = FastDFSTools.manyDownloads(urlList);
                }
                // 响应输出流，输出文件流
                // 获取到流信息
                String fileName = fileStream.getFileName();
                InputStream fileInputStream = fileStream.getFileInputStream();
                // 设置响应头信息，告知浏览器MIME类型和以附件的方式下载（不是直接展示在浏览器）
                resp.setContentType(Files.probeContentType(Paths.get(fileName)));
                String agent = resp.getHeader("user-agent") == null ? "Mozilla/5.0" : resp.getHeader("user-agent");
                String fileNameCoding = getFileName(agent, fileName);
                resp.setHeader("content-disposition", "attachment;filename=" + fileNameCoding);
                // 写出文件
                ServletOutputStream outputStream = resp.getOutputStream();
                byte[] bytes = new byte[FastDFSTools.BUFFER_FILE_SIZE];
                int index = 0;
                while ((index = fileInputStream.read(bytes)) != -1) {
                    outputStream.write(bytes, 0, index);
                }
                // 关闭流信息
                fileInputStream.close();
            } else {
                throw new IllegalArgumentException("请求参数异常，无法进行下载");
            }
        } catch (IOException e) {
            throw new RuntimeException("文件下载失败：" + e.getMessage());
        }
    }

    /***
     * 文件下载的文件名称存在中文则进行URL编码
     * @param agent 指定user-agent请求头
     * @param filename 文件名称
     * @return 编译后的文件名称
     * @throws UnsupportedEncodingException 编码异常
     */
    private String getFileName(String agent, String filename) throws UnsupportedEncodingException {
        if (agent.contains("MSIE")) {
            filename = URLEncoder.encode(filename, "utf-8");
            filename = filename.replace("+", " ");
        } else if (agent.contains("Firefox")) {
            // 编码文件名为 Base64
            String encodedFilename = Base64.getEncoder().encodeToString(filename.getBytes(StandardCharsets.UTF_8));
            // 创建带有 MIME 头部的编码后的文件名
            filename = "=?utf-8?B?" + encodedFilename + "?=";
        } else {
            filename = URLEncoder.encode(filename, "utf-8");
        }
        return filename;
    }
}
